﻿using IronPython.Hosting;
using Microsoft.Scripting.Hosting;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace LunarSF.SHomeWorkshop.LunarMarkdownEditor
{
    /// <summary>
    /// Python 脚本位置。
    /// </summary>
    public enum PythonScriptLocation
    {
        /// <summary>
        /// 全局脚本。
        /// </summary>
        Global,
        /// <summary>
        /// 工作区脚本。
        /// </summary>
        Workspace,
    }

    public class PythonScriptListItem : ListBoxItem
    {
        public PythonScriptLocation Location { get; set; } = PythonScriptLocation.Global;

        private static BitmapImage runScriptIcon;
        private static BitmapImage readOnlyOpenIcon;
        private static BitmapImage openAndEditIcon;
        private static BitmapImage locateIcon;
        private static BitmapImage deleteIcon;
        private static BitmapImage copyFromLmeScript;
        private static BitmapImage keyImage;

        static PythonScriptListItem()
        {
            runScriptIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/runit.png"));
            readOnlyOpenIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/readOnlyOpen.png"));
            openAndEditIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/edit.png"));
            locateIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/target.png"));
            deleteIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/delete.png"));
            copyFromLmeScript = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/copy-from-lmescript.png"));
            keyImage = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/shortcut.png"));
        }

        public PythonScriptListItem(string title, string description, string callModeText, string fullPath,
            string shortCut = "", string toolBarButtonText = "", Brush foreground = null, Brush background = null, string groupName = "", string popupButtonGroupsText = "")
        {
            this.popupButtonGroupsText = popupButtonGroupsText;

            this.Padding = new Thickness(0);
            this.MaxWidth = 396;

            this.Style = this.FindResource("MetroListBoxItem") as Style;
            this.Margin = new Thickness(0, 1, 0, 1);

            btnRunScript = new Button()
            {
                ToolTip = "运行脚本",
                Width = 32,
                Height = 32,
                Padding = new Thickness(0),
                Margin = new Thickness(2),
            };
            btnRunScript.Style = this.FindResource("MetroCircleButtonStyle") as Style;
            btnRunScript.BorderThickness = new Thickness(0);
            btnRunScript.Background = Brushes.WhiteSmoke;
            btnRunScript.BorderBrush = Brushes.SaddleBrown;
            btnRunScript.BorderThickness = new Thickness(1);
            btnRunScript.Content = new Image() { Width = 24, Height = 24, Source = runScriptIcon, };
            btnRunScript.Click += BtnRunScript_Click;
            btnRunScript.VerticalAlignment = VerticalAlignment.Stretch;
            headerWrapPanel.Children.Add(btnRunScript);
            DockPanel.SetDock(btnRunScript, Dock.Left);

            Expander baseExpander = new Expander()
            {
                IsExpanded = false,
                Background = Brushes.Transparent,
                BorderThickness = new Thickness(1),
            };
            baseExpander.Header = headerWrapPanel;

            btnEditScriptFile = new Button()
            {
                Width = 32,
                Height = 32,
                Background = Brushes.WhiteSmoke,
                Content = new Image()
                {
                    Source = openAndEditIcon,
                    Height = 24,
                    Width = 24,
                },
                BorderThickness = btnRunScript.BorderThickness,
                Style = this.FindResource("MetroCircleButtonStyle") as Style,
                BorderBrush = Brushes.SaddleBrown,
                FontWeight = FontWeights.Normal,
                Margin = new Thickness(0),
            };
            btnEditScriptFile.Click += BtnEdit_Click;
            headerWrapPanel.Children.Add(btnEditScriptFile);
            DockPanel.SetDock(btnEditScriptFile, Dock.Left);

            StackPanel sp = new StackPanel() { Orientation = Orientation.Horizontal, };

            btnDeleteCustomScriptOrCopyLmeScript = new Button()
            {
                Width = 32,
                Height = 32,
                Background = Brushes.WhiteSmoke,
                Content = new Image()
                {
                    Source = deleteIcon,
                    Height = 24,
                    Width = 24,
                },
                BorderThickness = btnRunScript.BorderThickness,
                BorderBrush = Brushes.SaddleBrown,
                Style = btnEditScriptFile.Style,  //this.FindResource("LSquareButtonStyle") as Style,
                FontWeight = FontWeights.Normal,
                Margin = new Thickness(2),
            };
            btnDeleteCustomScriptOrCopyLmeScript.Click += BtnDeleteCustomScriptOrCopyLmeScript_Click;
            sp.Children.Add(btnDeleteCustomScriptOrCopyLmeScript);

            btnLocateScriptFileInExplorer = new Button()
            {
                Width = 32,
                Height = 32,
                Background = Brushes.WhiteSmoke,
                Content = new Image()
                {
                    Source = locateIcon,
                    Height = 24,
                    Width = 24,
                },
                ToolTip = "在 Explorer 中定位脚本文件",
                BorderThickness = btnRunScript.BorderThickness,
                BorderBrush = Brushes.SaddleBrown,
                Style = btnEditScriptFile.Style,  //this.FindResource("LSquareButtonStyle") as Style,
                FontWeight = FontWeights.Normal,
                Margin = new Thickness(2),
            };
            btnLocateScriptFileInExplorer.Click += BtnLocateInExplorer_Click;
            sp.Children.Add(btnLocateScriptFileInExplorer);

            sp.VerticalAlignment = VerticalAlignment.Center;
            DockPanel.SetDock(sp, Dock.Right);
            headerWrapPanel.Children.Add(sp);

            StackPanel sp2 = new StackPanel()
            {
                Orientation = Orientation.Vertical,
                Margin = new Thickness(2),
            };

            // 使用很粗暴的绑定，效果不好
            //var binding = new Binding();
            //binding.Source = Globals.MainWindow.lbxPythonScripts;
            //binding.Path = new PropertyPath("ActualWidth");
            //binding.Converter = new LunarMarkdownEditor.Converter.MaxWidthOfListBoxItem();
            //binding.Mode = BindingMode.OneWay;
            //BindingOperations.SetBinding(tbTitle, TextBlock.MaxWidthProperty, binding);
            //BindingOperations.SetBinding(tbDescription, TextBlock.MaxWidthProperty, binding);
            //BindingOperations.SetBinding(tbCallMode, TextBlock.MaxWidthProperty, binding);

            tbTitle.VerticalAlignment = VerticalAlignment.Center;
            tbTitle.MouseEnter += TbTitle_MouseEnter;
            tbTitle.MouseLeave += TbTitle_MouseLeave;
            var bdBase = new Border()
            {
                Child = tbTitle,
                BorderBrush = Brushes.White,
                BorderThickness = new Thickness(1, 1, 1, 1),
            };
            bdTitle.Child = bdBase;
            headerWrapPanel.Children.Add(bdTitle);
            DockPanel.SetDock(bdTitle, Dock.Left);
            sp2.Children.Add(tbDescription);
            sp2.Children.Add(tbCallMode);
            sp2.Children.Add(tbScriptAuthor);
            sp2.Children.Add(tbShortCut);
            sp2.Children.Add(tbGroupName);

            baseExpander.Content = sp2;
            DockPanel.SetDock(sp2, Dock.Left);
            DockPanel.SetDock(sp, Dock.Right);

            this.Content = baseExpander;
            this.callMode = ConvertToPythonScriptCallMode(callModeText);

            RefreshCallMode();

            this.headerForeground = foreground;
            RefreshHeaderForeground();

            this.headerBackground = background;
            RefreshHeaderBackground();

            this.groupName = groupName;
            RefreshGroupName();

            this.title = title;  //注意这四行的顺序
            this.FullPath = fullPath;
            tbTitle.Text = this.Title;
            tbDescription.Text = description;
            this.toolBarButtonText = toolBarButtonText;

            this.shortCutText = shortCut;
            RefreshShortCut();

            MenuItem miRunScript = new MenuItem()
            {
                Style = TryFindResource("MetroMenuItem") as Style,
                Header = "执行此脚本(_R)",
                Icon = new Image()
                {
                    Source = runScriptIcon
                },
            };
            contextMenu.Items.Add(miRunScript);
            miRunScript.Click += MiRunScript_Click;

            contextMenu.Items.Add(new Separator());

            MenuItem miEditOrView = new MenuItem()
            {
                Style = TryFindResource("MetroMenuItem") as Style,
                Header = isLmePyScript ? "查看【内置】脚本(_V)" : "编辑〖自定义〗脚本(_E)",
                Icon = new Image()
                {
                    Source = isLmePyScript ? readOnlyOpenIcon : openAndEditIcon
                },
            };
            contextMenu.Items.Add(miEditOrView);
            miEditOrView.Click += MiEditOrView_Click;

            MenuItem miDeleteOrCopyAsCustomScript = new MenuItem()
            {
                Style = TryFindResource("MetroMenuItem") as Style,
                Header = isLmePyScript ? "据此创建〖自定义〗脚本(_C)" : "删除〖自定义〗脚本(_D)",
                Icon = new Image()
                {
                    Source = isLmePyScript ? copyFromLmeScript : deleteIcon
                },
            };
            contextMenu.Items.Add(miDeleteOrCopyAsCustomScript);
            miDeleteOrCopyAsCustomScript.Click += MiDeleteOrCopyAsCustomScript_Click;

            MenuItem miLocateInExplorer = new MenuItem()
            {
                Style = TryFindResource("MetroMenuItem") as Style,
                Header = "在 Explorer 中定位脚本文件(_P)",
                Icon = new Image()
                {
                    Source = locateIcon
                },
            };
            contextMenu.Items.Add(miLocateInExplorer);
            miLocateInExplorer.Click += MiPointInExplorer_Click;

            this.ContextMenu = contextMenu;
            tbTitle.MouseLeftButtonDown += TbTitle_MouseLeftButtonDown;
            tbTitle.MouseLeftButtonUp += TbTitle_MouseLeftButtonUp;
        }

        private void TbTitle_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            e.Handled = true;
            tbTitle.Background = Brushes.White;
            tbTitle.Foreground = Brushes.Black;
            if (tbTitlePressed)
            {
                BtnRunScript_Click(sender, e);
            }
            tbTitlePressed = false;
        }

        private void TbTitle_MouseLeave(object sender, MouseEventArgs e)
        {
            if (tbTitleForeground != null)
            {
                tbTitle.Foreground = tbTitleForeground;
                tbTitleForeground = null;
            }
            tbTitle.Background = Brushes.Transparent;
            tbTitlePressed = false;
        }

        private Brush tbTitleForeground = null;

        private void TbTitle_MouseEnter(object sender, MouseEventArgs e)
        {
            tbTitleForeground = tbTitle.Foreground;
            tbTitle.Foreground = Brushes.Black;
            tbTitle.Background = Brushes.White;
            tbTitlePressed = false;
        }

        /// <summary>
        /// 用于判断标题是否在标题内部被按下。
        /// </summary>
        private bool tbTitlePressed = false;

        private void TbTitle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            tbTitlePressed = true;
            tbTitle.Background = Brushes.LightGray;
            tbTitle.Foreground = Brushes.Black;
            e.Handled = true;  // 这行不能放在里面。否则 baseExpand 总会展开/折叠。
        }

        private void MiPointInExplorer_Click(object sender, RoutedEventArgs e)
        {
            BtnLocateInExplorer_Click(sender, e);
        }

        private void MiDeleteOrCopyAsCustomScript_Click(object sender, RoutedEventArgs e)
        {
            BtnDeleteCustomScriptOrCopyLmeScript_Click(sender, e);
        }

        private void MiRunScript_Click(object sender, RoutedEventArgs e)
        {
            BtnRunScript_Click(sender, e);
        }

        private void MiEditOrView_Click(object sender, RoutedEventArgs e)
        {
            BtnEdit_Click(sender, e);
        }

        private void BtnLocateInExplorer_Click(object sender, RoutedEventArgs e)
        {
            System.Diagnostics.Process.Start("explorer.exe", "/select, \"" + this.FullPath + "\"");
        }

        private Button btnRunScript;

        private Button btnDeleteCustomScriptOrCopyLmeScript;

        private Button btnLocateScriptFileInExplorer;

        private Button btnEditScriptFile;

        public PythonScriptCallMode ConvertToPythonScriptCallMode(string modeText)
        {
            switch (modeText)
            {
                case "1":
                case "１":
                    {
                        tbCallMode.Text = "调用方式：编译 Html 文件前自动调用";
                        return PythonScriptCallMode.BeforeCompileToHtml;
                    }
                case "2":
                case "２":
                    {
                        tbCallMode.Text = "调用方式：编译 Html 文件后自动调用";
                        return PythonScriptCallMode.AfterCompiledToHtml;
                    }
                case "3":
                case "３":
                    {
                        tbCallMode.Text = "调用方式：按 Ctrl + F11 调用";
                        return PythonScriptCallMode.RunByShortcuts;
                    }
                case "4":
                case "４":
                    {
                        tbCallMode.Text = "Html 工厂处理完毕前调用";
                        return PythonScriptCallMode.BeforeHtmlFactoryProcessed;
                    }
                case "5":
                case "５":
                    {
                        tbCallMode.Text = "按下 Enter 键时调用";
                        return PythonScriptCallMode.EnterPressing;
                    }
                //    {
                //        tbCallMode.Text = "调用方式：随 LME 启动时调用";
                //        return PythonScriptCallMode.AutoLoadWhenStart;
                //    }
                default:
                    {
                        tbCallMode.Text = "调用方式：手动调用";
                        return PythonScriptCallMode.Default;
                    }
            }
        }

        public void RefreshCallMode(PythonScriptCallMode? mode = null)
        {
            PythonScriptCallMode cMode = callMode;

            if (mode.HasValue)
            {
                this.callMode = mode.Value;
                cMode = mode.Value;
            }

            switch (cMode)
            {
                case PythonScriptCallMode.BeforeCompileToHtml:
                    tbCallMode.Text = "调用方式：编译 Html 前自动调用";
                    tbTitle.ToolTip = null;
                    bdTitle.BorderBrush = Brushes.Transparent;
                    break;
                case PythonScriptCallMode.AfterCompiledToHtml:
                    tbCallMode.Text = "调用方式：编译 Html 后自动调用";
                    tbTitle.ToolTip = null;
                    bdTitle.BorderBrush = Brushes.Transparent;
                    break;
                case PythonScriptCallMode.RunByShortcuts:
                    tbCallMode.Text = "调用方式：按 Ctrl + F11 调用";
                    tbTitle.ToolTip = null;
                    bdTitle.BorderBrush = Brushes.Transparent;
                    break;
                case PythonScriptCallMode.BeforeHtmlFactoryProcessed:
                    tbCallMode.Text = "调用方式：Html 工厂加工前调用";
                    tbTitle.ToolTip = null;
                    bdTitle.BorderBrush = Brushes.Transparent;
                    break;
                case PythonScriptCallMode.EnterPressing:
                    tbCallMode.Text = "调用方式：按 Enter 调用";
                    tbTitle.ToolTip = null;
                    bdTitle.BorderBrush = Brushes.Transparent;
                    break;
                //case PythonScriptCallMode.AutoLoadWhenStart:
                //    tbCallMode.Text = "调用方式：随 LME 启动时调用";
                //    tbTitle.ToolTip = "【随 LME 启动】脚本通常不应手动调用！";
                //    bdTitle.BorderBrush = Brushes.SaddleBrown;
                //    break;
                //case PythonScriptCallMode.Default:
                default:
                    tbCallMode.Text = "调用方式：手动调用";
                    tbTitle.ToolTip = null;
                    bdTitle.BorderBrush = Brushes.Transparent;
                    break;
            }

        }

        public void RefreshHeaderForeground()
        {
            this.tbTitle.Foreground = (this.headerForeground == null) ? Brushes.Black : headerForeground;
        }

        private Brush headerForeground = null;
        /// <summary>
        /// 标头文本色彩。
        /// </summary>
        public Brush HeaderForeground
        {
            get
            { return this.headerForeground; }
            set
            {
                this.headerForeground = value;
                RefreshHeaderForeground();
            }
        }

        public void RefreshHeaderBackground()
        {
            this.headerWrapPanel.Background = (this.headerBackground == null) ? Brushes.Transparent : headerBackground;
        }

        private Brush headerBackground = null;
        /// <summary>
        /// 标头文本背景色彩。
        /// </summary>
        public Brush HeaderBackground
        {
            get { return this.headerBackground; }
            set
            {
                this.headerBackground = value;
                RefreshHeaderBackground();
            }
        }

        private string groupName = "";
        /// <summary>
        /// 组名，用以将脚本分组，排序时同一组的在一起。
        /// </summary>
        public string GroupName
        {
            get { return groupName ?? ""; }
            set
            {
                this.groupName = value;
            }
        }

        public void RefreshGroupName()
        {
            if (string.IsNullOrWhiteSpace(this.groupName))
            {
                this.tbTitle.ToolTip = null;
                tbGroupName.Text = "分组名称：<无>";
            }
            else
            {
                this.tbTitle.ToolTip =
                    tbGroupName.Text = "分组名称：" + this.GroupName;
            }
        }

        public void RefreshShortCut()
        {
            if (string.IsNullOrWhiteSpace(this.shortCutText.ToUpper()))
            {
                tbShortCut.Text = "　快捷键：<无>";
                tbShortCut.Visibility = Visibility.Collapsed;
            }
            else
            {
                tbShortCut.Text = "　快捷键：" + this.shortCutText.ToUpper();
                TbTitle.Inlines.Add(new LineBreak());
                tbTitle.Inlines.Add(new Image()
                {
                    Height = 14,
                    Width = 14,
                    Source = keyImage,
                });
                TbTitle.Inlines.Add(new Span(new Run(shortCutText.ToUpper())));
                tbShortCut.Visibility = Visibility.Visible;
            }
        }

        /// <summary>
        /// 调用 Python 脚本的方式（时机）。
        /// </summary>
        public enum PythonScriptCallMode
        {
            /// <summary>
            /// [默认值]手动调用。
            /// </summary>
            Default,
            /// <summary>
            /// 在编译Html之前自动调用。
            /// </summary>
            BeforeCompileToHtml,
            /// <summary>
            /// 编译为Html文本后自动调用。
            /// </summary>
            AfterCompiledToHtml,
            /// <summary>
            /// 当用户按下快捷键时调用。
            /// </summary>
            RunByShortcuts,
            /// <summary>
            /// 在 Html Factory 处理过 Html 文件后进一步调用 Python 脚本处理。
            /// </summary>
            BeforeHtmlFactoryProcessed,
            /// <summary>
            /// 当 LME 主窗口载入完毕时自动调用。
            /// 用于编写插件程序，可以添加菜单项、工具条按钮等。
            /// </summary>
            //AutoLoadWhenStart, // 有“IronPython 调用 C# 方法写错时无法截获异常”的问题，无法解决。
            /// <summary>
            /// 按下 Enter 键之前，通常用于校验当前行已输入的文本是否合法。
            /// </summary>
            EnterPressing,
        }

        private PythonScriptCallMode callMode = PythonScriptCallMode.Default;
        /// <summary>
        /// 脚本应何时调用。
        /// </summary>
        public PythonScriptCallMode CallMode { get { return this.callMode; } }

        private string shortCutText = "";
        /// <summary>
        /// 脚本的快捷键。
        /// </summary>
        public string ShortCutText
        {
            get { return this.shortCutText; }
            set { this.shortCutText = value; }
        }

        private DockPanel headerWrapPanel = new DockPanel()
        {
            HorizontalAlignment = HorizontalAlignment.Stretch,
            VerticalAlignment = VerticalAlignment.Stretch,
            LastChildFill = true,
        };

        private ContextMenu contextMenu = new ContextMenu();

        private TextBlock tbTitle = new TextBlock()
        {
            TextWrapping = TextWrapping.Wrap,
            HorizontalAlignment = HorizontalAlignment.Stretch,
            Padding = new Thickness(2, 2, 2, 2),
        };

        private Border bdTitle = new Border()
        {
            BorderBrush = Brushes.Transparent,
            BorderThickness = new Thickness(1),
            SnapsToDevicePixels = true,
            VerticalAlignment = VerticalAlignment.Center,
        };

        private TextBlock tbShortCut = new TextBlock()
        {
            Margin = new Thickness(20, 6, 0, 0),
            TextWrapping = TextWrapping.Wrap,
            Foreground = Brushes.SaddleBrown,
            HorizontalAlignment = HorizontalAlignment.Left,
        };

        private TextBlock tbGroupName = new TextBlock()
        {
            Margin = new Thickness(20, 6, 0, 0),
            TextWrapping = TextWrapping.Wrap,
            Foreground = Brushes.SaddleBrown,
            HorizontalAlignment = HorizontalAlignment.Left,
        };

        public TextBlock TbTitle { get { return tbTitle; } }

        private TextBlock tbDescription = new TextBlock()
        {
            Margin = new Thickness(20, 6, 0, 0),
            TextWrapping = TextWrapping.Wrap,
            Foreground = Brushes.SaddleBrown,
            HorizontalAlignment = HorizontalAlignment.Left,
        };

        public TextBlock TbDescription { get { return tbDescription; } }

        private TextBlock tbCallMode = new TextBlock()
        {
            Margin = new Thickness(20, 6, 0, 0),
            TextWrapping = TextWrapping.Wrap,
            Foreground = Brushes.SaddleBrown,
            HorizontalAlignment = HorizontalAlignment.Left,
        };

        public TextBlock TbCallMode { get { return tbCallMode; } }

        private TextBlock tbScriptAuthor = new TextBlock()
        {
            Margin = new Thickness(20, 6, 0, 0),
            TextWrapping = TextWrapping.Wrap,
            Foreground = Brushes.SaddleBrown,
        };

        private string popupButtonGroupsText = null;
        /// <summary>
        /// 用户自定义按钮组（支持多组）。
        /// </summary>
        public string PopupButtonGroupsText
        {
            get { return popupButtonGroupsText; }
            set { popupButtonGroupsText = value; }
        }

        public TextBlock TbScriptAuthor { get { return this.tbScriptAuthor; } }

        private void BtnRunScript_Click(object sender, RoutedEventArgs e)
        {
            RunScript(this.CallMode, this.FullPath);
        }

        /// <summary>
        /// 相当于按下运行按钮。
        /// </summary>
        /// <param name="groupName">使用快捷工具条按钮调用时才有必要提供值。</param>
        /// <param name="args">使用快捷工具条按钮调用时才有必要提供。</param>
        /// <returns></returns>
        internal PythonResult RunScript(string groupName = null, string args = null)
        {
            return RunScript(this.CallMode, this.FullPath, true, groupName, args);
        }

        public static PythonResult RunScript(PythonScriptCallMode callMode, string scriptFullPath, bool showErrorMessage = true, string groupName = null, string args = null)
        {
            // 自启动脚本存在致命缺陷，无法截获某些异常，会导致程序直接崩溃。
            //if (callMode == PythonScriptCallMode.AutoLoadWhenStart)
            //{
            //    var answer = LMessageBox.Show("被设置为【随 LME 启动时调用】的脚本通常不应该手动调用（调试代码时例外）。\r\n\r\n　　真的要继续吗？",
            //        Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning);
            //    if (answer != MessageBoxResult.Yes) return null;
            //}

            var activeEdit = Globals.MainWindow.ActivedEditor;
            if (activeEdit == null)
            {
                if (showErrorMessage)
                {
                    LMessageBox.Show("未打开任何文档！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return null;
                }
                else
                {
                    return new PythonResult() { ErrInfo = "未打开任何 Markdown 文件！", };
                }
            }

            string text;
            ScriptEngine engine = Python.CreateEngine();
            ScriptScope scope = engine.CreateScope();

            bool? cancel = false;
            try
            {
                if (Globals.MainWindow.rbtnSelectedText.IsChecked == true)
                {
                    // 相对于工作区根目录的路径
                    string directoryLevelMark = CustomMarkdownSupport.GetDirectoryLevelMark(activeEdit.FullFilePath);

                    text = activeEdit.EditorBase.SelectedText;

                    var sourceCode = engine.CreateScriptSourceFromFile(scriptFullPath);
                    // 运行脚本
                    scope.SetVariable("inputText", text);
                    scope.SetVariable("outputText", text);
                    scope.SetVariable("documentText", activeEdit.EditorBase.Text);
                    scope.SetVariable("ignoreTextChanging", false);
                    scope.SetVariable("pathToWorkspace", directoryLevelMark);
                    scope.SetVariable("pathOfWorkspace", Globals.PathOfWorkspace);
                    scope.SetVariable("iv_cancel", false);

                    if (string.IsNullOrWhiteSpace(groupName) == false)
                    {
                        scope.SetVariable("popButtonGroupName", groupName);
                    }

                    if (string.IsNullOrWhiteSpace(args) == false)
                    {
                        scope.SetVariable("popButtonArgs", args);
                    }

                    var fi = new FileInfo(activeEdit.FullFilePath);
                    scope.SetVariable("psArgs", new PythonScriptArgs()
                    {
                        MdFileInfo = fi,
                    });
                    // 重设搜索路径，默认的不管用
                    var pathList = engine.GetSearchPaths();
                    pathList.Add(Globals.PathOfPythonScripts);
                    pathList.Add(Globals.PathOfWorkspacePythonScripts);
                    pathList.Add(Globals.InstalledPath);
                    engine.SetSearchPaths(pathList);

                    var streamOut = new MemoryStream();
                    var streamErr = new MemoryStream();
                    engine.Runtime.IO.SetOutput(streamOut, Encoding.Default);
                    engine.Runtime.IO.SetErrorOutput(streamErr, Encoding.Default);

                    sourceCode.Execute<string>(scope);

                    try
                    {
                        try
                        {
                            if (callMode == PythonScriptCallMode.EnterPressing)
                            {
                                cancel = scope.GetVariable<bool>("iv_cancel");
                            }
                            else cancel = false;
                        }
                        catch
                        {
                            cancel = false;
                        }
                        // 这个要放前面

                        var ignoreTextChanging = scope.GetVariable<bool>("ignoreTextChanging");
                        var result = "";
                        if (ignoreTextChanging == false)
                        {
                            result = scope.GetVariable<string>("outputText");
                            //if (result == null)
                            //    throw new ArgumentNullException("outputText");  //  现在，即使注释掉 outputText 也不要紧了——它变成内置变量了。
                            activeEdit.EditorBase.BeginChange();
                            activeEdit.EditorBase.SelectedText = result.ToString();
                            activeEdit.EditorBase.EndChange();
                        }

                        return new PythonResult()
                        {
                            OutputInfo = System.Text.Encoding.Default.GetString(streamOut.ToArray()),
                            ErrInfo = System.Text.Encoding.Default.GetString(streamErr.ToArray()),
                            InputTextValue = text,
                            OutputTextValue = result,
                            Cancel = cancel.HasValue ? cancel.Value : false,
                        };
                    }
                    catch (AccessViolationException avex)
                    {
                        if (showErrorMessage)
                        {
                            LMessageBox.Show(avex.Message);
                        }
                        else
                        {
                            return new PythonResult() { ErrInfo = avex.Message, };
                        }
                    }
                }
                else if (Globals.MainWindow.rbtnActiveDocument.IsChecked == true)
                {
                    // 相对于工作区根目录的路径
                    string directoryLevelMark = CustomMarkdownSupport.GetDirectoryLevelMark(activeEdit.FullFilePath);

                    text = activeEdit.EditorBase.Text;

                    var sourceCode = engine.CreateScriptSourceFromFile(scriptFullPath);
                    // 运行脚本
                    scope.SetVariable("inputText", text);
                    scope.SetVariable("outputText", text);
                    scope.SetVariable("documentText", text);
                    scope.SetVariable("ignoreTextChanging", false);
                    scope.SetVariable("pathToWorkspace", directoryLevelMark);
                    scope.SetVariable("pathOfWorkspace", Globals.PathOfWorkspace);
                    scope.SetVariable("iv_cancel", false);

                    var fi = new FileInfo(activeEdit.FullFilePath);
                    scope.SetVariable("psArgs", new PythonScriptArgs()
                    {
                        MdFileInfo = fi,
                    });

                    var streamOut = new MemoryStream();
                    var streamErr = new MemoryStream();
                    engine.Runtime.IO.SetOutput(streamOut, Encoding.Default);
                    engine.Runtime.IO.SetErrorOutput(streamErr, Encoding.Default);

                    // 重设搜索路径，默认的不管用
                    var pathList = engine.GetSearchPaths();
                    pathList.Add(Globals.PathOfPythonScripts);
                    pathList.Add(Globals.PathOfWorkspacePythonScripts);
                    pathList.Add(Globals.InstalledPath);
                    engine.SetSearchPaths(pathList);

                    sourceCode.Execute<string>(scope);

                    try
                    {
                        if (callMode == PythonScriptCallMode.EnterPressing)
                        {
                            cancel = scope.GetVariable<bool>("iv_cancel");
                        }
                        else cancel = false;
                    }
                    catch
                    {
                        cancel = false;
                    }
                    // 这个要放前面

                    var ignoreTextChanging = scope.GetVariable<bool>("ignoreTextChanging");
                    var result = "";
                    if (ignoreTextChanging == false)
                    {
                        result = scope.GetVariable<string>("outputText");
                        //if (result == null)
                        //    throw new ArgumentNullException("outputText");  //  现在，即使注释掉 outputText 也不要紧了——它变成内置变量了。
                        activeEdit.EditorBase.BeginChange();
                        activeEdit.EditorBase.Document.Text = result.ToString();
                        activeEdit.EditorBase.EndChange();
                    }

                    return new PythonResult()
                    {
                        OutputInfo = System.Text.Encoding.Default.GetString(streamOut.ToArray()),
                        ErrInfo = System.Text.Encoding.Default.GetString(streamErr.ToArray()),
                        InputTextValue = text,
                        OutputTextValue = result,
                        Cancel = cancel.HasValue ? cancel.Value : false,
                    };
                }
                else if (Globals.MainWindow.rbtnOpenedDocument.IsChecked == true)
                {
                    PythonResult pResult = null;
                    foreach (var ue in Globals.MainWindow.mainTabControl.Items)
                    {
                        var edit = ue as MarkdownEditor;
                        if (edit == null) continue;

                        // 相对于工作区根目录的路径
                        string directoryLevelMark = CustomMarkdownSupport.GetDirectoryLevelMark(edit.FullFilePath);

                        var sourceCode = engine.CreateScriptSourceFromFile(scriptFullPath);
                        // 运行脚本
                        scope.SetVariable("inputText", edit.EditorBase.Text);
                        scope.SetVariable("outputText", edit.EditorBase.Text);
                        scope.SetVariable("documentText", edit.EditorBase.Text);
                        scope.SetVariable("ignoreTextChanging", false);
                        scope.SetVariable("pathToWorkspace", directoryLevelMark);
                        scope.SetVariable("pathOfWorkspace", Globals.PathOfWorkspace);
                        scope.SetVariable("iv_cancel", false);

                        var fi = new FileInfo(activeEdit.FullFilePath);
                        scope.SetVariable("psArgs", new PythonScriptArgs()
                        {
                            MdFileInfo = fi,
                        });

                        var streamOut = new MemoryStream();
                        var streamErr = new MemoryStream();
                        engine.Runtime.IO.SetOutput(streamOut, Encoding.Default);
                        engine.Runtime.IO.SetErrorOutput(streamErr, Encoding.Default);

                        // 重设搜索路径，默认的不管用
                        var pathList = engine.GetSearchPaths();
                        pathList.Add(Globals.PathOfPythonScripts);
                        pathList.Add(Globals.PathOfWorkspacePythonScripts);
                        pathList.Add(Globals.InstalledPath);
                        engine.SetSearchPaths(pathList);

                        sourceCode.Execute<string>(scope);

                        try
                        {
                            if (callMode == PythonScriptCallMode.EnterPressing)
                            {
                                cancel = scope.GetVariable<bool>("iv_cancel");
                            }
                            else cancel = false;
                        }
                        catch
                        {
                            cancel = false;
                        }
                        // 这个要放前面

                        var ignoreTextChanging = scope.GetVariable<bool>("ignoreTextChanging");
                        var result = "";
                        if (ignoreTextChanging == false)
                        {
                            result = scope.GetVariable<string>("outputText");
                            //if (result == null)
                            //    throw new ArgumentNullException("outputText");  //  现在，即使注释掉 outputText 也不要紧了——它变成内置变量了。
                            edit.EditorBase.BeginChange();
                            edit.EditorBase.Document.Text = result.ToString();
                            edit.EditorBase.EndChange();
                        }

                        pResult = new PythonResult()
                        {
                            OutputInfo = System.Text.Encoding.Default.GetString(streamOut.ToArray()),
                            ErrInfo = System.Text.Encoding.Default.GetString(streamErr.ToArray()),
                            InputTextValue = edit.EditorBase.Text,
                            OutputTextValue = result,
                            Cancel = cancel.HasValue ? cancel.Value : false,
                        };
                    }

                    return pResult;  // 只返回最后一个，如果中途出了问题，自有 try{}catch(){} 解决。
                }
                else
                {
                    if (showErrorMessage)
                    {
                        LMessageBox.Show("请选择脚本作用范围！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        return null;
                    }
                    else
                    {
                        return new PythonResult() { ErrInfo = "请选择脚本作用范围！", };
                    }
                }
            }
            catch (Exception ex)
            {
                ExceptionOperations eo = engine.GetService<ExceptionOperations>();
                string error = eo.FormatException(ex);
                if (showErrorMessage)
                {
                    LMessageBox.Show(error, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return null;
                }
                else
                {
                    return new PythonResult() { ErrInfo = error, Cancel = cancel.HasValue ? cancel.Value : false, };
                }
            }

            return null;
        }

        private bool isLmePyScript = false;
        /// <summary>
        /// [只读]是否 LME 内置的示例脚本文件。
        /// </summary>
        public bool IsLmePyScript { get { return this.isLmePyScript; } }

        private void BtnDeleteCustomScriptOrCopyLmeScript_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            if (isLmePyScript)
            {
                try
                {
                    if (File.Exists(this.fullPath) == false)
                    {
                        LMessageBox.Show("发生意外，找不到对应的内置脚本文件了！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        return;
                    }

                    var srcFileInfo = new FileInfo(this.fullPath);
                    if (srcFileInfo.Name.ToLower().StartsWith("lme_") == false || srcFileInfo.Name.Length < 4)
                    {
                        LMessageBox.Show("发生意外，对应源文件名不符合内置脚本文件名命名规则！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        return;
                    }

                    var destFileInfo = new FileInfo(srcFileInfo.Directory + "\\" + srcFileInfo.Name.Substring(4));
                    if (destFileInfo.Exists)
                    {
                        LMessageBox.Show("已经存在同名自定义脚本！请先删除之前创建的同名脚本。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        return;
                    }

                    File.Copy(srcFileInfo.FullName, destFileInfo.FullName);
                    var newItem = new PythonScriptListItem(
                        this.title.StartsWith("【内置】") ? this.title.Substring(4) : this.title,
                        this.tbDescription.Text, tbCallMode.Text, destFileInfo.FullName);
                    //newItem.Location = PythonScriptLocation.Global;  // 默认就是 Global 脚本。
                    Globals.MainWindow.lbxPythonScripts.Items.Insert(0, newItem);
                }
                catch (Exception ex)
                {
                    LMessageBox.Show(ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                }
                return;
            }

            bool isOpen = false;
            Window editWindow = null;
            foreach (var ownedWindow in Globals.MainWindow.OwnedWindows)
            {
                var pyEdit = ownedWindow as PythonEditor;
                if (pyEdit == null) continue;
                if (pyEdit.FullPath.ToLower() == this.fullPath.ToLower())
                {
                    isOpen = true;
                    editWindow = pyEdit;
                    break;
                }
            }

            if (isOpen)
            {
                LMessageBox.Show("这个脚本正在某个脚本编辑器窗口中进行编辑，请先关闭该编辑器窗口。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                editWindow?.Activate();
                return;
            }

            var result = LMessageBox.Show("真的要删除此脚本文件吗？", Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning);
            if (result != MessageBoxResult.Yes) return;

            if (File.Exists(this.FullPath))
            {
                try
                {
                    File.Delete(this.FullPath);
                    switch (this.Location)
                    {
                        case PythonScriptLocation.Workspace:
                            {
                                for (int i = Globals.MainWindow.workspacePyScriptsToolBar.Items.Count - 1; i >= 0; i--)
                                {
                                    var item = Globals.MainWindow.workspacePyScriptsToolBar.Items[i] as Button;
                                    if (item == null) continue;
                                    if (item.Tag == this)
                                    {
                                        Globals.MainWindow.workspacePyScriptsToolBar.Items.Remove(item);
                                    }
                                }

                                if (Globals.MainWindow.lbxWorkspacePythonScripts.Items.Contains(this))
                                {
                                    Globals.MainWindow.lbxWorkspacePythonScripts.Items.Remove(this);
                                }
                                break;
                            }
                        // PythonScriptLocation.Global:
                        default:
                            {
                                for (int i = Globals.MainWindow.pyScriptsToolBar.Items.Count - 1; i >= 0; i--)
                                {
                                    var item = Globals.MainWindow.pyScriptsToolBar.Items[i] as Button;
                                    if (item == null) continue;
                                    if (item.Tag == this)
                                    {
                                        Globals.MainWindow.pyScriptsToolBar.Items.Remove(item);
                                    }
                                }

                                if (Globals.MainWindow.lbxPythonScripts.Items.Contains(this))
                                {
                                    Globals.MainWindow.lbxPythonScripts.Items.Remove(this);
                                }
                                break;
                            }
                    }
                }
                catch (Exception ex)
                {
                    LMessageBox.Show(ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                }
            }
        }

        private void BtnEdit_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            EditScriptFile();
        }

        internal void EditScriptFile()
        {
            try
            {
                if (File.Exists(this.FullPath) == false)
                {
                    LMessageBox.Show($"Python 脚本文件【{this.FullPath}】不存在！", Globals.AppName, System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Warning);
                    return;
                }

                var info = new FileInfo(this.FullPath);
                if (info.Extension.ToLower() != ".py")
                {
                    LMessageBox.Show($"Python 脚本文件【{info.Name}】不存在！", Globals.AppName, System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Warning);
                    return;
                }

                if (this.Editor != null)
                {
                    LMessageBox.Show($"脚本【{this.Title}】的编辑器窗口已经被打开。请勿重复打开编辑器窗口！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
                    this.Editor.Activate();
                    return;
                }
            }
            catch (Exception ex)
            {
                LMessageBox.ShowWarning("发生意外，无法打开已初始化的编辑器窗口！异常消息如下：\r\n　　" + ex.Message);
                return;
            }

            var editor = new PythonEditor(this.FullPath, tbTitle.Text, this)
            {
                Owner = Globals.MainWindow,
                MasterItem = this,
            };
            this.Editor = editor;
            editor.Show();
        }

        public PythonEditor Editor { get; set; } = null;

        private string fullPath = "";
        /// <summary>
        /// [读写]对应的 Python 脚本文件的完整磁盘路径。
        /// </summary>
        public string FullPath
        {
            get { return this.fullPath; }
            set
            {
                this.fullPath = value;
                RefreshFullPath();
            }
        }

        private string title = "";

        public string Title
        {
            get { return this.title; }
            set
            {
                this.title = value;
                this.tbTitle.Text = value;
            }
        }

        private string toolBarButtonText = "";

        public string ToolBarButtonText
        {
            get { return this.toolBarButtonText; }
            set { this.toolBarButtonText = value; }
        }

        private void RefreshFullPath()
        {
            if (File.Exists(fullPath) == false)
            {
                isLmePyScript = false;
                if (this.title.StartsWith("【内置】"))
                    this.title = this.title.Substring(4);
                btnRunScript.IsEnabled =
                    btnEditScriptFile.IsEnabled =
                    btnDeleteCustomScriptOrCopyLmeScript.IsEnabled =
                    btnLocateScriptFileInExplorer.IsEnabled = false;
            }
            else
            {
                var fi = new FileInfo(this.fullPath);
                isLmePyScript = fi.Name.ToLower().StartsWith("lme_");
                if (isLmePyScript)
                {
                    if (this.title.StartsWith("【内置】") == false)
                        this.title = "【内置】" + this.title;
                    btnEditScriptFile.Content = new Image()
                    {
                        Height = 24,
                        Width = 24,
                        Source = readOnlyOpenIcon,
                    };
                    btnEditScriptFile.ToolTip = "查看【内置】脚本";
                    btnDeleteCustomScriptOrCopyLmeScript.Content = new Image()
                    {
                        Height = 24,
                        Width = 24,
                        Source = copyFromLmeScript,
                        ToolTip = "据此创建〖自定义〗脚本",
                    };
                    tbScriptAuthor.Text = "脚本来源：LME 内置的示例脚本";
                }
                else
                {
                    if (this.title.StartsWith("【内置】"))
                        this.title = this.title.Substring(4);
                    btnEditScriptFile.Content = new Image()
                    {
                        Height = 24,
                        Width = 24,
                        Source = openAndEditIcon,
                    };
                    btnEditScriptFile.ToolTip = "编辑〖自定义〗脚本";
                    btnDeleteCustomScriptOrCopyLmeScript.Content = new Image()
                    {
                        Height = 24,
                        Width = 24,
                        Source = deleteIcon,
                        ToolTip = "删除〖自定义〗脚本",
                    };
                    tbScriptAuthor.Text = "脚本来源：用户编写的自定义脚本";
                }
            }
        }

        /// <summary>
        /// 最后修改时间。
        /// </summary>
        public DateTime LastWriteTime { get; set; }

        internal bool RunScriptByShortCut(bool isShift, bool isAlt, bool isCtrl, Key key)
        {
            if (string.IsNullOrWhiteSpace(ShortCutText)) return false;

            var spans = ShortCutText.Split(new char[] { '+', }, StringSplitOptions.RemoveEmptyEntries);
            if (spans.Length < 1) return false;

            string keyText = spans[spans.Length - 1].Trim(new char[] { ' ', '　', '\t' });
            if (string.IsNullOrWhiteSpace(keyText)) return false;

            bool hasShift = false;
            bool hasCtrl = false;
            bool hasAlt = false;

            foreach (var s in spans)
            {
                var span = s.ToLower();
                if (span.Contains("shift")) hasShift = true;
                if (span.Contains("alt")) hasAlt = true;
                if (span.Contains("ctrl")) hasCtrl = true;
            }

            if (hasShift != isShift) return false;
            if (hasCtrl != isCtrl) return false;
            if (hasAlt != isAlt) return false;

            if (keyText.ToLower() != key.ToString().ToLower()) return false;

            var result = RunScript(this.CallMode, this.FullPath);
            return string.IsNullOrEmpty(result.ErrInfo);
        }
    }

    public class ComparsionPythonScriptItem : IComparer<PythonScriptListItem>
    {
        public int Compare(PythonScriptListItem x, PythonScriptListItem y)
        {
            if (x.GroupName == y.GroupName)
            {
                return y.LastWriteTime.CompareTo(x.LastWriteTime);
            }
            return y.GroupName.CompareTo(x.GroupName);
        }
    }

    public class ComparsionLastWriteTime : IComparer<FileInfo>
    {
        public int Compare(FileInfo x, FileInfo y)
        {
            return y.LastWriteTime.CompareTo(x.LastWriteTime);
        }
    }

}
